﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;

namespace NetPublisher
{
	public class LogFile
	{
		#region Private fields

		private bool _append = true;
		private EventLog _eventLog;
		private bool _firstError = true;
		private LogType _logType = LogType.File;
		private bool _writeStackTrace = true;
		private static object _lockObj = new object();

		#endregion

		#region Public Properties

		/// <summary>
		/// Name of current log file
		/// </summary>
		public string FileName { get; private set; }

		/// <summary>
		/// Gets the Last Exception, that was thrown.
		/// </summary>
		public string LastError { get; private set; }

		/// <summary>
		/// Indicate that need to write Stack Trace into log exception message. By default is true.
		/// </summary>
		public bool WriteStackTrace
		{
			get { return _writeStackTrace; }
			set { _writeStackTrace = value; }
		}

		/// <summary>
		/// Type of Log. Indicate, how to need log messages or errors
		/// </summary>
		public LogType LogType
		{
			get { return _logType; }
			set { _logType = value; }
		}

		/// <summary>
		/// Gets or Sets the Name of current log. If Use Application Event Log Type that use this name as Source for EventLog
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Gets or Sets the values, that indicates to append log entries, when using log to File.
		/// </summary>
		public bool Append
		{
			get { return _append; }
			set { _append = value; }
		}

		#endregion

		#region Public Methods

		public bool WriteException(Exception ex)
		{
			return WriteException(ex, null);
		}

		/// <summary>
		/// Write exception to current Log
		/// </summary>
		/// <param name="ex">Exception to log</param>
		/// <param name="description">Description about current exception. Can be null</param>
		/// <returns>true, if log successfull</returns>
		public bool WriteException(Exception ex, string description)
		{
			return Write(PrepareExceptionMessage(ex, description), LogEntryType.Error);
		}

		/// <summary>
		/// Write exception to current Log
		/// </summary>
		/// <param name="message">Message to show</param>
		/// <returns>true, if log successfull</returns>
		public bool Write(string message)
		{
			return Write(message, LogEntryType.Information);
		}

		/// <summary>
		/// Write exception to current Log
		/// </summary>
		/// <param name="message">Message to show</param>
		/// <param name="type">use only if in messagebox need show error Icon</param>
		/// <returns>true, if log successfull</returns>
		public bool Write(string message, LogEntryType type)
		{
			lock (_lockObj)
			{
				LastError = message;
				switch (_logType)
				{
					case LogType.File:
						return WriteToFile(message);

					case LogType.ApplicationEventLog:
						return WriteToApplicationEventLog(message, type);

					case LogType.None:
						return true;

					default:
						return false;
				}
			}
		}

		#endregion

		#region Static Fields

		private static List<LogFile> _allLogs;
		private static LogFile _default;

		#endregion

		#region Static Properties

		/// <summary>
		/// Default Log.
		/// For change default log file setting, call SetDefultLog static method
		/// </summary>
		public static LogFile Default
		{
			get
			{
				if (_default == null)
				{
					lock (_lockObj)
					{
						if (_default == null)
						{
							var filePath = string.Format(CultureInfo.InvariantCulture,
							                             "application.log",
							                             DateTime.Now.ToString("dd-MM-yyyy-HH-mm-ss"));
							SetDefaultLog(filePath, true);
						}
					}
				}
				return _default;
			}
		}

		/// <summary>
		/// List of available logs
		/// </summary>
		public static ICollection<LogFile> AllLogs
		{
			get { return _allLogs ?? (_allLogs = new List<LogFile>()); }
		}

		#endregion

		#region Static Methods

		public static void SetDefaultLog(string fileName, bool append)
		{
			_default = new LogFile {FileName = GetLocalPath(fileName), Append = append};
		}

		#endregion

		#region Help Methods

		/// <summary>
		/// Combine path of current executable file with addPath
		/// </summary>
		/// <param name="addPath">Path, thet to add to current executable file</param>
		/// <returns>Combined path</returns>
		private static string GetLocalPath(string addPath)
		{
			return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, addPath);
		}

		/// <summary>
		/// Prepare Exception message to write to log
		/// </summary>
		/// <param name="ex">Exception that need to log</param>
		/// <param name="description">Description. Can be null</param>
		/// <returns>Prepared message that can be wreite to log</returns>
		private string PrepareExceptionMessage(Exception ex, string description)
		{
			var exBuilder = new StringBuilder();
			exBuilder.AppendFormat("[{0}] Exception of type {1} was thrown. \r\n Message: {2}",
			                       DateTime.Now, ex.GetType().Name, ex.Message);
			if (description != null)
				exBuilder.AppendFormat("\tDescription: {0}\r\n", description);
			exBuilder.AppendLine();

			Exception inner = ex.InnerException;
			while (inner != null)
			{
				exBuilder.AppendFormat("\tInner Exception of type {0} Message: {1}\r\n",
				                       ex.GetType().Name, ex.Message);
				inner = inner.InnerException;
			}
			if (_writeStackTrace)
				exBuilder.AppendFormat("\tStack Trace of Exception: \r\n{0}\r\n\r\n", ex.StackTrace);
			return exBuilder.ToString();
		}

		/// <summary>
		/// Write to File Log
		/// </summary>
		/// <param name="message">Message to log</param>
		/// <returns>True, if log was successfull</returns>
		private bool WriteToFile(string message)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(FileName));
			using (var writer = new StreamWriter(FileName, _append))
			{
				if (_firstError)
				{
					writer.WriteLine("\r\n----------------------------------Start Log at {0}-----------------------------------\r\n",
					                 DateTime.Now);
					_firstError = false;
				}
				writer.WriteLine(message);
			}
			return true;
		}

		/// <summary>
		/// Write to File Log
		/// </summary>
		/// <param name="message">Message to log</param>
		/// <returns>True, if log was successfull</returns>
		/// <param name="type">The Entry type of error. Used for icon in messagebox or for EventLog.</param>
		private bool WriteToApplicationEventLog(string message, LogEntryType type)
		{
			if (_eventLog == null)
				_eventLog = new EventLog("Application") { Source = Name ?? AppDomain.CurrentDomain.FriendlyName };

			_eventLog.WriteEntry(message, (EventLogEntryType) type);
			return true;
		}

		#endregion

		#region TextWriter 

		private TextWriter _textWriter;

		public TextWriter DefaultTextWriter
		{
			get { return _textWriter ?? (_textWriter = new LogFileTextWriter(this)); }
		}

		public class LogFileTextWriter:TextWriter
		{
			public LogFileTextWriter(LogFile parent)
			{
				if (parent == null)
					throw new ArgumentNullException("parent");
				ParentLogFile = parent;
			}

			public LogFile ParentLogFile { get; set; }

			public override Encoding Encoding
			{
				get { return Encoding.Default; }
			}

			public override void Write(string value)
			{
				if (value == null)
					return;
				ParentLogFile.Write(value);
			}
		}

		#endregion
	}

	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
	public enum LogEntryType
	{
		None = 0,
		/// <summary>
		/// An error event. This indicates a significant problem the user should know
		/// about; usually a loss of functionality or data.
		/// </summary>
		Error = 1,
		/// <summary>
		/// A warning event. This indicates a problem that is not immediately significant,
		/// but that may signify conditions that could cause future problems.
		/// </summary>
		Warning = 2,
		/// <summary>
		///     An information event. This indicates a significant, successful operation.
		/// </summary>
		Information = 4,
		/// <summary>
		///     A success audit event. This indicates a security event that occurs when an
		///     audited access attempt is successful; for example, logging on successfully.
		/// </summary>
		SuccessAudit = 8,
		/// <summary>
		///     A failure audit event. This indicates a security event that occurs when an
		///     audited access attempt fails; for example, a failed attempt to open a file.
		/// </summary>
		FailureAudit = 16,
	}

	public enum LogType
	{
		/// <summary>
		/// If Log Entry Type is None - log will be disabled.
		/// </summary>
		None,
		File,
		ApplicationEventLog
	}
}
